rest-framework框架的介绍
- rest-framework 是根据 RESTful 规范开发出来的一套 Django 的 API 框架
- rest-framework 是建立在CBV基础上使用的 -> 通俗理解: 如果要是用 rest-framework,那么就一定要使用 CBV 去编写视图
- rest-framework 的作用: 开发符合 RESTful 规范的接口
- rest-framework 的10个功能(按请求顺序排序): 路由 视图 版本 认证 权限 频率 解析器 序列化 分页 渲染器
csrf token 的问题
- 在使用 rest-framework 编写接口的时候会默认忽略掉 csrf token 认证中间件
- 原理: 在 .as_view() 返回 view 视图函数的时候会在 view 视图函数上加上 csrf_exempt 装饰器,使其忽略 csrf token 的认证

- 如果在编写接口的时候还是发生了 csrf token 问题的解决办法
- 问题一: Postman 可以正常发送请求,但是在浏览器上无法正常发送
- 解决办法一: 以 vue 项目为例: 查看 cookie 中是否存在 csrf token 的相关 cookie,如果存在清楚浏览器所有 cookie 即可
- 解决办法二: 以 vue 项目为例: 先注释掉 csrf token 中间件,然后查看浏览器是否可以正常发送请求,如果可以,删除掉 csrf token 中间件的注释,给视图类下的视图函数单独添加 csrf_exempt 装饰器
from rest_framework.views import APIView
from rest_framework.response import Response
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
class Test(APIView):
@method_decorator(csrf_exempt) # 给类里面的方法加装饰器 需要导入一个方法method_decorator
def post(self, request):
return Response({'a': 1})
- 问题一: Postman 可以无法发送请求,浏览器上也无法正常发送
- 解决办法: 先注释掉 csrf token 中间件,然后查看Postman是否可以正常发送请求,如果可以,删除掉 csrf token 中间件的注释,给视图类下的视图函数单独添加 csrf_exempt 装饰器
本章节所使用的数据库
# models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
type_choices = ((1, '普通用户'), (2, 'VIP'), (3, 'SVIP'))
user_type = models.IntegerField(choices=type_choices, default=1)
def __str__(self):
return self.username
class Meta:
verbose_name = "用户表"
verbose_name_plural = verbose_name
class Token(models.Model):
user = models.OneToOneField(to='User')
token = models.CharField(max_length=128)
def __str__(self):
return self.token
class Meta:
verbose_name = "Token表"
verbose_name_plural = verbose_name
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name='书名')
price = models.IntegerField(verbose_name='价格')
pub_date = models.DateField(verbose_name='出版日期')
production_date = models.DateTimeField(verbose_name='生产日期', null=True)
publish = models.ForeignKey("Publish", verbose_name='出版社')
authors = models.ManyToManyField("Author", verbose_name='作者')
def __str__(self):
return self.title
class Meta:
verbose_name = "书籍表"
verbose_name_plural = verbose_name
class Publish(models.Model):
name = models.CharField(max_length=32, verbose_name='出版社名称')
email = models.EmailField(verbose_name='邮箱')
def __str__(self):
return self.name
class Meta:
verbose_name = "出版社表"
verbose_name_plural = verbose_name
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
def __str__(self):
return self.name
class Meta:
verbose_name = "作者表"
verbose_name_plural = verbose_name
rest-framework 的下载与配置
1. 下载 rest-framework
pip3 install djangorestframework -i https://pypi.douban.com/simple # 使用豆瓣的镜像
2. 注册 rest-framework
# settings.py
INSTALLED_APPS = [
……
'rest_framework',
]
接口类的编写规范
- 在编写接口的时候要注意不能出现异常导致程序运行中断,而是使用 try 对异常进行捕获防治程序运行中断
- 编写好每个异常的状态码(即: code),每个状态码就代表着一个错误信息
- 注意: 在本章节中只有这里使用了接口类编写规范,其他栏目没有使用需要注意,且一定要按照该规范编写
# response.py
# 返回给前端的数据结构
class BaseResponse(object):
def __init__(self):
self.code = 0
self.data = None
self.msg = ''
@property
def dic(self):
return self.__dict__
# exceptions.py
# 自定义异常类
class CommonException(Exception):
def __init__(self, code, msg):
self.code = code
self.msg = msg
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.core.exceptions import ObjectDoesNotExist # 如果查询不到数据会抛出该错误
from api.utils.response import BaseResponse
from api.utils.exceptions import CommonException # 自定义异常类
class Spec(APIView):
def get(self):
res = BaseResponse()
try:
obj1 = 表类.objects.get(pk=xxx) # .get() 方法如果查询不到就会抛出 ObjectDoesNotExist 异常
obj2 = 表类.objects.filter(pk=xxx).first() # 如果查询不到不会抛出异常,只会返回 None
if not obj2:
raise CommonException(1002, 'xxx不存在2')
res.data = {
'xxx': obj1.xxx,
'xxx': obj2.xxx
}
except ObjectDoesNotExist as e:
res.msg = 'xxx不存在1'
res.code = 1001
except CommonException as e:
res.msg = e.msg
res.code = e.code
except Exception as e:
print(e)
# 捕获其他不知道的异常,然后写入日志中
return Response(res.dic)
序列化组件
只要继承了 APIView 就可以使用序列化组件
1. 序列化组件的基本使用
- 序列化组件 的使用和 form 组件的使用类似,都需要定义一个类,而这个类就叫: 序列化类
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def get(self, request):
publish_obj = Publish.objects.all()
ps = PublishSerializers(publish_obj, many=True) # 通过你所创建的 序列化类 序列化数据
data = ps.data # Serializer对象.data 获取序列化后的数据 -> [OrderedDict([('name', '东莞出版社'), ('email', '123@qq.com')]), OrderedDict([('name', '广州出版社'), ('email', '123@qq.com')])]
return Response(data)
# 接口: http://127.0.0.1:8000/publish/
# 请求类型: GET
# 结果:
[
{
"name": "东莞出版社",
"email": "123@qq.com"
},
{
"name": "广州出版社",
"email": "123@qq.com"
}
]
2. many 参数
- many 参数: 说明所要序列化的数据是 queryset对象(查询到的数据列表)还是 查询到的数据对象
- many=True -> 所要序列化的数据是 queryset对象(查询到的数据列表)
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def get(self, request):
publish_obj = Publish.objects.all()
ps = PublishSerializers(publish_obj, many=True) # 所要序列化的数据是 queryset对象(查询到的数据列表)
data = ps.data
return Response(data)
# 接口: http://127.0.0.1:8000/publish/
# 请求类型: GET
# 结果:
[
{
"name": "东莞出版社",
"email": "123@qq.com"
},
{
"name": "广州出版社",
"email": "123@qq.com"
}
]
- many=False -> 所要序列化的数据是查询到的数据对象,且 many 参数默认值为False,所以在序列化查询到的数据对象的时候 many 参数可以不用传
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def get(self, request):
publish_obj = Publish.objects.all().first()
# ps = PublishSerializers(publish_obj, many=False) # 所要序列化的数据是查询到的数据对象
ps = PublishSerializers(publish_obj) # 所要序列化的数据是查询到的数据对象
data = ps.data
return Response(data)
# 接口: http://127.0.0.1:8000/publish/
# 请求类型: GET
# 结果:
{
"name": "东莞出版社",
"email": "123@qq.com"
}
3. Response() 方法
- rest-framework 所提供的 Response() 方法实际上也是继承了 HttpResponse ,只是在 HttpResponse 的基础上扩展了一些功能
- 如果使用了 rest-framework 那么就使用它所提供的 Response() 方法将数据发送给前端
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def get(self, request):
publish_obj = Publish.objects.all().first()
ps = PublishSerializers(publish_obj)
data = ps.data
return Response(data)
- Response() 方法可以直接对 字典、列表、元祖、字符串、数字 进行发送
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class PublishView(APIView):
def get(self, request):
return Response({'a': 1, 'b': 2})
# return Response([1, 2, 3])
# return Response((1, 2, 3))
# return Response('字符串')
# return Response(10)
4.序列化日期对象并且处理日期格式
- format='日期格式'
# serializer.py
from rest_framework import serializers
class BookSerializers(serializers.Serializer):
title = serializers.CharField()
price = serializers.CharField()
pub_date = serializers.CharField()
production_date = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S')
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
data = bs.data
return Response(data)
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"title": "三国演义",
"price": "100",
"pub_date": "2019-08-11",
"production_date": "2019-08-11 18:07:19"
},
{
"title": "红楼梦",
"price": "200",
"pub_date": "2019-08-11",
"production_date": "2019-08-11 18:08:06"
}
]
4.Serializer 类的 depth 参数说明
- 作用: 序列化 一对多 或 多对多 字段
- depth 的缺点: 获取很多与当前接口无用的字段数据(即: 获取到一对多或多对多字段中的全部数据)
- depth 值的说明: 必须为数值
- 当前值为0: 序列化出来的是 一对多 或 多对多字段 所保存的 id 值
- 当前值为1: 序列化出来的是 一对多 或 多对多字段 所对应的表的数据
- 当前值为2: 如果 一对多 或 多对多字段 中还有 一对多 或 多对多字段,那么就序列化出该 一对多 或 多对多字段 所对应的表的数据
- 当前数值为n: ……
- depth 的默认值: 0
- depth 官方取值范围推荐: 1-10
- depth = 0
# serializer.py
from rest_framework import serializers
from .models import *
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
depth = 1
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import *
from .models import *
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
return Response(bs.data)
# depth = 0
# 接口: http://127.0.0.1:8001/book/
# 请求类型: GET
# 结果:
[
{
"id": 2,
"title": "三体(第二部)",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
1,
2
]
},
{
"id": 5,
"title": "三国演义",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
1,
2
]
}
]
# --------------------------------------------------------------
# depth = 1
# 接口: http://127.0.0.1:8001/book/
# 请求类型: GET
# 结果:
[
{
"id": 2,
"title": "三体(第二部)",
"price": 100,
"pub_date": "2012-12-12",
"publish": {
"id": 1,
"name": "东莞出版社",
"email": "123@qq.com"
},
"authors": [
{
"id": 1,
"name": "Kevin",
"age": 18
},
{
"id": 2,
"name": "Aimer",
"age": 20
}
]
},
{
"id": 5,
"title": "三国演义",
"price": 100,
"pub_date": "2012-12-12",
"publish": {
"id": 1,
"name": "东莞出版社",
"email": "123@qq.com"
},
"authors": [
{
"id": 1,
"name": "Kevin",
"age": 18
},
{
"id": 2,
"name": "Aimer",
"age": 20
}
]
}
]
5.Serializer 类的 source 参数说明
- source 参数一般用于序列化 choices 字段 和 一对多字段
- source 参数可以序列化自身字段,但是没有意义
- 即: username = serializers.CharField(source='username') 等同于 username = serializers.CharField()
- source 参数的值支持像模板语言中的链式操作
- source='get_choices字段_display' -> source='get_user_type_display'
- source='一对多字段名.字段' -> source='publish.name'
- source='多对多字段名.all' -> source='authors.all'
6. 序列化 choices 字段的方法
- 使用 source 参数序列化 choices 字段
- 如果不使用 source 参数序列化 choices 字段,那么在序列化 choices 字段的时候默认会取与该字段所保存的数值
- 语法: source='get_choices字段_display'
# serializer.py
from rest_framework import serializers
class UserSerializers(serializers.Serializer):
username = serializers.CharField()
user_type = serializers.CharField(source='get_user_type_display')
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import *
from .models import *
class UserView(APIView):
def get(self, request):
user_list = User.objects.all()
us = UserSerializers(user_list, many=True)
print(us.data) # [OrderedDict([('username', 'Kevin'), ('user_type', 'SVIP')]), OrderedDict([('username', 'Aimer'), ('user_type', 'VIP')]), OrderedDict([('username', 'Jack'), ('user_type', '普通用户')])]
return Response(us.data)
# 接口: http://127.0.0.1:8001/user/
# 请求类型: GET
# 结果:
[
{
"username": "Kevin",
"user_type": "SVIP"
},
{
"username": "Aimer",
"user_type": "VIP"
},
{
"username": "Jack",
"user_type": "普通用户"
}
]
7.SerializerMethodField 自定义字段
- SerializerMethodField() 字段方法的作用: 自定义字段的返回值
- 使用场景: 当我们想返回一些自定义的值的时候,就可以使用 SerializerMethodField
- 函数名定义规则: get_字段名
# serializer.py
from rest_framework import serializers
class BookSerializers(serializers.Serializer):
title = serializers.CharField()
price = serializers.CharField()
custom_field = serializers.SerializerMethodField()
def get_custom_field(self, obj):
"""
:param obj: obj 就是数据库中的一条数据
:return: 所要显示的数据
"""
price = obj.price
return price + 100
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
print(bs.data) # [OrderedDict([('title', '三体'), ('price', '100'), ('custom_field', 200)]), OrderedDict([('title', '七大罪'), ('price', '50'), ('custom_field', 150)])]
return Response(bs.data)
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"title": "三体",
"price": "100",
"custom_field": 200
},
{
"title": "七大罪",
"price": "50",
"custom_field": 150
}
]
8.context 参数(即: 在 serializers 中获取 request 对象)
- 视图类没有使用 serializer_class 参数
- 通过 context 参数 serializers 可以接收视图传递过来的参数
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True, context={'request': request, 'username': 'Kevin', 'age': 18})
return Response(bs.data)
# serializers.py
class BookSerializers(serializers.Serializer):
title = serializers.CharField()
price = serializers.CharField()
custom_field = serializers.SerializerMethodField()
def get_custom_field(self, obj):
print(self.context) # {'request': <rest_framework.request.Request object at 0x0000025F75F0D668>, 'age': 18, 'username': 'Kevin'}
request = self.context.get('request')
username = self.context.get('username')
age = self.context.get('age')
return 1
- 视图类使用了 serializer_class 参数
- 当视图类使用了 serializer_class 参数,那么 serializers 默认只会接收到三个参数:
- request -> request 对象,当前视图的请求对象
- format -> 当前请求期望返回的数据格式
- view -> 当前请求的类视图对象,相当于视图类中的 self
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework import viewsets
class BookView(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
# serializers.py
class BookSerializers(serializers.Serializer):
title = serializers.CharField()
price = serializers.CharField()
custom_field = serializers.SerializerMethodField()
def get_custom_field(self, obj):
print(self.context) # {'view': <api.views.BookView object at 0x00000229E4FCA5C0>, 'request': <rest_framework.request.Request object at 0x00000229E5077898>, 'format': None}
request = self.context.get('request')
response_format = self.context.get('format')
view = self.context.get('view')
return 1
9. 序列化一对多字段的方法 -> 正向查询
- 使用 source 参数序列化一对多字段
- 如果不使用 source 参数序列化一对多字段,那么在序列化一对多字段的时候默认会取与该字段所关联的表类里面的 __str__ 的返回值
- 语法: source='一对多字段名.字段'
# serializer.py
from rest_framework import serializers
class BookSerializers(serializers.Serializer):
title = serializers.CharField()
price = serializers.CharField()
pub_date = serializers.CharField()
publish = serializers.CharField(source='publish.name') # 序列化一对多字段,且在序列化一对多字段的时候默认取 publish 表下的 name 字段的数据
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
print(bs.data) # [OrderedDict([('title', '西游记'), ('price', '111'), ('pub_date', None), ('publish', '东莞出版社')]), OrderedDict([('title', '三体'), ('price', '222'), ('pub_date', None), ('publish', '东莞出版社')])]
return Response(bs.data)
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"title": "三体",
"price": "222",
"pub_date": null,
"publish": "东莞出版社"
},
{
"title": "三国演义",
"price": "100",
"pub_date": "2012-12-12",
"publish": "东莞出版社"
}
]
10. 序列化一对多字段的方法 -> 反向查询
- 使用 SerializerMethodField() 字段方法,自定义函数返回值,实现序列化一对多字段的反向查询
- SerializerMethodField() 字段方法的作用: 自定义字段的返回值
- 函数名定义规则: get_字段名
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
book = serializers.SerializerMethodField()
def get_book(self, obj):
"""
:param obj: 当前数据的对象(即: 和查询到的数据对象是一样的)
"""
temp = []
for book in obj.book_set.all(): # 反向查询
temp.append({
'title': book.title,
'price': book.price,
'pub_date': book.pub_date,
})
return temp
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import *
from .models import *
class PublishView(APIView):
def get(self, request):
publish_list = Publish.objects.all()
ps = PublishSerializers(publish_list, many=True)
return Response(ps.data)
# 接口: http://127.0.0.1:8001/publish/
# 请求类型: GET
# 结果:
[
{
"name": "东莞出版社",
"email": "123@qq.com",
"book": [
{
"pub_date": "2012-12-12",
"price": 100,
"title": "三体(第二部)"
},
{
"pub_date": "2012-12-12",
"price": 100,
"title": "三国演义"
},
{
"pub_date": "2012-12-12",
"price": 100,
"title": "红楼梦"
}
]
},
{
"name": "广州出版社",
"email": "123@qq.com",
"book": []
}
]
11. 序列化多对多字段的方法 -> 正向查询
- 使用 SerializerMethodField() 字段方法,自定义函数返回值,实现序列化多对多字段的正向查询
- SerializerMethodField() 字段方法的作用: 自定义字段的返回值
- 函数名定义规则: get_字段名
# serializer.py
from rest_framework import serializers
class BookSerializers(serializers.Serializer):
title = serializers.CharField()
price = serializers.CharField()
pub_date = serializers.CharField()
authors = serializers.SerializerMethodField()
def get_authors(self, obj):
"""
:param obj: 当前数据的对象(即: 和查询到的数据对象是一样的)
"""
temp = []
for author in obj.authors.all():
temp.append(author.name)
return temp
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
print(bs.data) # [OrderedDict([('title', '西游记'), ('price', '111'), ('pub_date', None), ('authors', ['Kevin'])]), OrderedDict([('title', '三体'), ('price', '222'), ('pub_date', None), ('authors', ['Aimer'])])]
return Response(bs.data)
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"title": "三体",
"price": "222",
"pub_date": null,
"authors": [
"Aimer"
]
},
{
"title": "三国演义",
"price": "100",
"pub_date": "2012-12-12",
"authors": [
"Kevin",
"Aimer"
]
}
]
12. 序列化多对多字段的方法 -> 反向查询
- 使用 SerializerMethodField() 字段方法,自定义函数返回值,实现序列化多对多字段的反向查询
- SerializerMethodField() 字段方法的作用: 自定义字段的返回值
- 函数名定义规则: get_字段名
# serializer.py
from rest_framework import serializers
class AuthorSerializers(serializers.Serializer):
name = serializers.CharField()
age = serializers.IntegerField()
book = serializers.SerializerMethodField()
def get_book(self, obj):
"""
:param obj: 当前数据的对象(即: 和查询到的数据对象是一样的)
"""
temp = []
for book in obj.book_set.all(): # 反向查询
temp.append({
'title': book.title,
'price': book.price,
'pub_date': book.pub_date,
})
return temp
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import *
from .models import *
class AuthorView(APIView):
def get(self, request):
author_list = Author.objects.all()
aus = AuthorSerializers(author_list, many=True)
return Response(aus.data)
# 接口: http://127.0.0.1:8001/author/
# 请求类型: GET
# 结果:
[
{
"name": "Kevin",
"age": 18,
"book": [
{
"title": "红楼梦",
"price": 100,
"pub_date": "2012-12-12"
},
{
"title": "勿忘我",
"price": 100,
"pub_date": "2012-12-12"
}
]
},
{
"name": "Aimer",
"age": 20,
"book": [
{
"title": "三体(第二部)",
"price": 100,
"pub_date": "2012-12-12"
},
{
"title": "三国演义",
"price": 100,
"pub_date": "2012-12-12"
},
{
"title": "超人",
"price": 100,
"pub_date": "2012-12-12"
}
]
}
]
13. 验证数据是否有误
- 一般都是验证提交过来的数据
- 这里的验证数据功能和Form组件或者ModelForm是一样的
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField(error_messages={'required': '出版社名称不能为空'})
email = serializers.CharField(error_messages={'required': '邮箱不能为空'})
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def post(self, request):
ps = PublishSerializers(data=request.data) # 将提交过来的数据传入data参数中进行验证
if ps.is_valid(): # 验证提交过来的数据
print(ps.validated_data) # 获取验证通过的数据 -> OrderedDict([('name', '深圳出版社'), ('email', '123@qq.com')])
publish = Publish.objects.create(**ps.validated_data) # 将数据保存到数据库
p_ps = PublishSerializers(publish) # 序列化数据
return Response(p_ps.data)
else:
print(ps.errors) # 获取验证失败后的错误信息
return Response(ps.errors)
# 接口说明: 添加出版社
# 接口: http://127.0.0.1:8000/publish/
# 请求类型: POST
# 发送数据的编码格式: application/json -> 即: json 格式
# 所要发送的数据: {"name":"上海出版社","email":"123@qq.com"}
# 结果:
{
"name": "上海出版社",
"email": "123@qq.com"
}
APIView 的 request 对象
1. APIView 的 request对象 说明
- 这里所指的request对象就是 FBV 或 CBV 中的 request 对象
- APIView(在 rest-framework 中 CBV 所要继承的类) 的 request 对象是在 View(即: 以前CBV中所继承的类) 的 request 对象的基础上进行封装的,所以在使用在使用 rest-framework 中的 CBV 里的 request 对象是一个全新的 request 对象
- View 的 request 对象有的功能,APIView 的 request 对象都有
2. APIView 的 request对象 所提供的常用属性:
- request.GET -> 获取GET请求参数
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request):
get_data = request.GET
# 等同于
# get_data = request._request.GET
print(get_data) # <QueryDict: {'b': ['2'], 'a': ['1']}>
return HttpResponse('get请求')
- request.query_params -> 获取GET请求参数
- request.query_params 等同于 request.GET
- request.query_params 实际上是执行了 request._request.GET
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request):
get_data = request.query_params
print(get_data) # <QueryDict: {'name': ['三国演义'], 'price': ['100']}>
print(get_data.get('name')) # 三国演义
print(get_data.get('price')) # 100
return HttpResponse('get请求')
- request.data -> 获取除GET请求外的其他请求参数(即: POST/PUT/PATCH 等请求的参数)
- 在不使用 rest-framework 之前,request.POST 只能获取以 application/x-www-form-urlencoded 编码格式发送过来的数据,其他编码格式发送过来的数据(如: application/json)都只能通过 request.body 获取,然后自行解析,但是在 rest-framework 可以通过 request.data 获取不同编码格式发送过来的数据,并且帮我们进行解析
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
class BookView(APIView):
def post(self, request):
data = request.data
print(data) # 以 application/json 编码格式提交过来的数据 -> {'name': 'Kevin', 'age': 18}
return HttpResponse('post请求')
- request._request -> 获取 View 的 request 对象(即: 旧的request对象)
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
class BookView(APIView):
def get(self, request):
get_data = request._request.GET
print(get_data) # <QueryDict: {'b': ['2'], 'a': ['1']}>
return HttpResponse('get请求')
def post(self, request):
post_data = request._request.POST
print(post_data) # <QueryDict: {'age': ['18'], 'name': ['Kevin']}>
return HttpResponse('post请求')
序列化数据的方式
这里的序列化数据指的是将 queryset对象 或 查询到的数据对象 转换成 列表 或 字典,然后将序列化好的数据发送给前端
1. 序列化 queryset 对象(查询到的数据列表)
- 方式一
# views.py
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from .models import *
class PublishView(APIView):
def get(self, request):
publish_queryset = Publish.objects.all().values('name', 'email')
publish_list = list(publish_queryset)
print(publish_list) # [{'name': '东莞出版社', 'email': '123@qq.com'}, {'name': '广州出版社', 'email': '123@qq.com'}]
return JsonResponse(publish_list, safe=False)
- 方式二
# views.py
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from django.forms.models import model_to_dict
from .models import *
class PublishView(APIView):
def get(self, request):
publish_queryset = Publish.objects.all()
publish_list = []
for publish in publish_queryset:
publish_list.append(model_to_dict(publish))
print(publish_list) # [{'id': 1, 'name': '东莞出版社', 'email': '123@qq.com'}, {'id': 2, 'name': '广州出版社', 'email': '123@qq.com'}]
return JsonResponse(publish_list, safe=False)
- 方式三
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from django.core import serializers
from .models import *
class PublishView(APIView):
def get(self, request):
publish_queryset = Publish.objects.all()
publish_list = serializers.serialize("json", publish_queryset)
print(publish_list) # [{"model": "app01.publish", "pk": 1, "fields": {"name": "\u4e1c\u839e\u51fa\u7248\u793e", "email": "123@qq.com"'+'}'+'}'}}, {"model": "app01.publish", "pk": 2, "fields": {"name": "\u5e7f\u5dde\u51fa\u7248\u793e", "email": "123@qq.com"'+'}'+'}'}}]
return HttpResponse(publish_list)
- 方式四 -> 推荐使用
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def get(self, request):
publish_queryset = Publish.objects.all().values('name', 'email')
ps = PublishSerializers(publish_queryset, many=True)
print(ps.data) # 获取序列化之后的数据 -> [OrderedDict([('name', '东莞出版社'), ('email', '123@qq.com')]), OrderedDict([('name', '广州出版社'), ('email', '123@qq.com')])]
return Response(ps.data)
2. 序列化查询到的数据对象
- 方式一
# views.py
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from django.forms.models import model_to_dict
from .models import *
class PublishView(APIView):
def get(self, request):
publish_obj = Publish.objects.all().first()
publish_dict = model_to_dict(publish_obj)
print(publish_dict) # {'email': '123@qq.com', 'name': '东莞出版社', 'id': 1}
return JsonResponse(publish_dict)
- 方式二
# serializer.py
from rest_framework import serializers
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class PublishView(APIView):
def get(self, request):
publish_obj = Publish.objects.all().first()
ps = PublishSerializers(publish_obj)
print(ps.data) # 获取序列化之后的数据 -> {'email': '123@qq.com', 'name': '东莞出版社'}
return Response(ps.data)
ModelSerializer
只要继承了 APIView 就可以使用 ModelSerializer
ModelSerializer 的使用和 ModelForm 组件的使用类似
1.ModelSerializer的基本使用
# serializer.py
from rest_framework import serializers
from .models import *
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
# 自定义错误信息
extra_kwargs = {
"title": {
'error_messages': {
'required': '书籍名称不能为空'
}
},
'price': {
'error_messages': {
'required': '价格不能为空'
}
},
'pub_date': {
'error_messages': {
'required': '日期不能为空'
}
},
'publish': {
'error_messages': {
'required': '出版社不能为空'
}
},
'author': {
'error_messages': {
'required': '作者不能为空'
}
}
}
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
# 查看所有书籍
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
return Response(bs.data)
# 添加书籍
def post(self, request):
bs = BookSerializers(data=request.data)
if bs.is_valid(): # 验证数据是否有误
print(bs.validated_data) # 验证过后正确的数据: OrderedDict([('title', '三国演义'), ('price', 100), ('pub_date', datetime.date(2012, 12, 12)), ('publish', <Publish: 东莞出版社>), ('authors', [<Author: Kevin>, <Author: Aimer>])])
bs.save() # 保存数据,实际上调用的是 rest-framework 中的 create 方法
return Response(bs.data)
else:
return Response(bs.errors) # bs.errors 验证过后的错误信息
class BookDetailView(APIView):
# 查看指定的书籍
def get(self, request, id):
book_obj = Book.objects.filter(pk=id).first()
if book_obj:
bs = BookSerializers(book_obj)
return Response(bs.data)
else:
return Response({'static': 0, 'message': '没有该书籍'})
# 修改书籍
def put(self, request, id):
book_obj = Book.objects.filter(pk=id).first()
bs = BookSerializers(book_obj, data=request.data) # 修改数据的时候一定要指定修改那条数据(即: book_obj),否则在执行 .save() 的时候就相当于新增数据
if bs.is_valid(): # 验证数据是否有误
bs.save() # 保存数据,实际上调用的是 rest-framework 中的 update 方法
return Response(bs.data)
else:
return Response(bs.errors) # bs.errors 验证过后的错误信息
# 删除书籍
def delete(self, request, id):
Book.objects.filter(pk=id).delete()
return Response()
# urls.py
urlpatterns = [
……
url(r'book/$', BookView.as_view()),
url(r'book/(\d+)', BookDetailView.as_view())
]
# 接口说明: 查看所有书籍
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"id": 2,
"title": "三体",
"price": 222,
"pub_date": null,
"publish": 1,
"authors": [
2
]
},
{
"id": 5,
"title": "三国演义",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
1,
2
]
}
]
# --------------------------------------------------------------
# 接口说明: 添加书籍
# 接口: http://127.0.0.1:8000/book/
# 请求类型: POST
# 发送数据的编码格式: application/json -> 即: json 格式
# 所要发送的数据: {"title":"勿忘我","price":100,"pub_date":"2012-12-12","publish":1,"authors":[1,2]}
# 结果:
{
"id": 14,
"title": "勿忘我",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
1,
2
]
}
# --------------------------------------------------------------
# 接口说明: 查看指定书籍
# 接口: http://127.0.0.1:8000/book/2
# 请求类型: GET
# 结果:
{
"id": 2,
"title": "三体",
"price": 222,
"pub_date": null,
"publish": 1,
"authors": [
2
]
}
# --------------------------------------------------------------
# 接口说明: 修改书籍
# 接口: http://127.0.0.1:8000/book/2
# 请求类型: PUT
# 发送数据的编码格式: application/json -> 即: json 格式
# 所要发送的数据: {"title":"三体(第二部)","price":100,"pub_date":"2012-12-12","publish":1,"authors":[1,2]}
# 结果:
{
"id": 2,
"title": "三体(第二部)",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
2,
1
]
}
# --------------------------------------------------------------
# 接口说明: 删除书籍
# 接口: http://127.0.0.1:8000/book/11
# 请求类型: DELETE
# 结果:
空
2.save() 方法的说明
- 执行 save() 之前一定要执行 is_valid() 方法,否则会报错
- .save() 方法的返回值就是所添加/修改的该条数据对象
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
# 添加书籍
def post(self, request):
bs = BookSerializers(data=request.data)
if bs.is_valid(): # 验证数据是否有误
data_obj = bs.save()
print(data_obj.title, data_obj.price) # 三体 100
return Response(bs.data)
else:
return Response(bs.errors)
3.自定制字段
- 当 fields = '__all__' 的时候,但是又想对里面的某些字段进行单独配置的时候,可以直接在 ModelSerializer 类中重新编写该字段,那么该字段的配置就会覆盖掉之前的配置
- 建议: 如果使用了 ModelSerializer 就尽量不要自定制字段,否则容易出现问题
- 注意:
- 当自定制一对多字段的时候,并且使用了source参数,那么就需要重写save()方法中的create()和update()方法
- 当自定制多对多字段的时候,添加或修改数据多对多字段的数据会有问题
# serializer.py
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
extra_kwargs = {
"title": {
'error_messages': {
'required': '书籍名称不能为空'
}
},
'price': {
'error_messages': {
'required': '价格不能为空'
}
},
'pub_date': {
'error_messages': {
'required': '日期不能为空'
}
},
'publish': {
'error_messages': {
'required': '出版社不能为空'
}
},
'author': {
'error_messages': {
'required': '作者不能为空'
}
}
}
publish = serializers.CharField(source="publish.name") # 重新编写 publish 字段的配置
4. 重写 save() 中的 create() 和 update() 方法
- .save()方法实际上调用的是 rest-framework 中的 create 或者 update 方法
- 重写create()/update()方法就是自己将提交过来的数据保存到数据库,并且返回当前保存的数据
- 什么时候需要重写
- 当使用了自定制字段,并且再字段方法中传递了source参数 -> 通俗理解: 当自定制一对多字段的时候,并且使用了source参数,那么就需要重写save()方法中的create()和update()方法
# serializer.py
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
extra_kwargs = {
"title": {
'error_messages': {
'required': '书籍名称不能为空'
}
},
'price': {
'error_messages': {
'required': '价格不能为空'
}
},
'pub_date': {
'error_messages': {
'required': '日期不能为空'
}
},
'publish': {
'error_messages': {
'required': '出版社不能为空'
}
},
'author': {
'error_messages': {
'required': '作者不能为空'
}
}
}
publish = serializers.CharField(source="publish.pk") # 重新编写 publish 字段的配置
# 重写 save() 中的 create() 方法
def create(self, validated_data):
"""
:param validated_data: 通过验证的数据
"""
print(validated_data)
book = Book.objects.create(title=validated_data['title'], price=validated_data['price'], pub_date=validated_data['pub_date'], publish_id=validated_data['publish']['pk'])
book.authors.add(*validated_data["authors"])
return book
# 重写 save() 中的 update() 方法
def update(self, instance, validated_data):
"""
:param instance: 需要修改的查询到的数据对象
:param validated_data: 通过验证的数据
"""
for key, val in validated_data.items():
if hasattr(instance, key):
if 'publish' == key:
instance.publish_id = validated_data['publish']['pk']
elif 'authors' == key:
instance.authors.set(validated_data['authors'])
else:
setattr(instance, key, val)
instance.save()
return instance
超链接API: Hyperlinked
1.HyperlinkedIdentityField 字段方法
- HyperlinkedIdentityField 字段方法一般都是用在一对多字段上
- HyperlinkedIdentityField 字段方法返回的是一个url路径
- HyperlinkedIdentityField 字段方法一般在前后端分离的时候很少会使用
- 使用场景: 分页器
- 注意: 如果你所定义的 序列化类 使用了 HyperlinkedIdentityField 字段方法,那么在实例化 序列化类 的时候一定要设置 context={'request': request},否则会报错
# urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'publish/(?P<pk>\d+)', PublishDetailView.as_view(), name='publish_detail'),
url(r'book/$', BookView.as_view())
]
# serializer.py
from rest_framework import serializers
from .models import *
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
# view_name -> url别名
# lookup_field -> 当前外键字段(一对多)在数据库中的字段名
# lookup_url_kwarg -> 分组命名匹配中的别名
publish = serializers.HyperlinkedIdentityField(view_name='publish_detail', lookup_field='publish_id', lookup_url_kwarg='pk')
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
# 查看所有书籍
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True, context={'request': request}) # 如果你所定义的 序列化类 使用了 HyperlinkedIdentityField 字段方法,那么在实例化 序列化类 的时候一定要设置 context={'request': request},否则会报错
return Response(bs.data)
class PublishDetailView(APIView):
# 查看指定的出版社
def get(self, request, pk):
publish_obj = Publish.objects.filter(pk=pk).first()
if publish_obj:
ps = PublishSerializers(publish_obj)
return Response(ps.data)
else:
return Response({'static': 0, 'message': '没有该出版社'})
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"id": 2,
"publish": "http://127.0.0.1:8000/publish/1",
"title": "三体",
"price": 222,
"pub_date": null,
"authors": [
2
]
},
……
]
视图四部曲
1. 混合使用
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
class BookView(APIView):
# 查看所有书籍
def get(self, request):
book_list = Book.objects.all()
bs = BookSerializers(book_list, many=True)
return Response(bs.data)
# 添加书籍
def post(self, request):
bs = BookSerializers(data=request.data)
if bs.is_valid(): # 验证数据是否有误
print(bs.validated_data) # 验证过后正确的数据: OrderedDict([('title', '三国演义'), ('price', 100), ('pub_date', datetime.date(2012, 12, 12)), ('publish', <Publish: 东莞出版社>), ('authors', [<Author: Kevin>, <Author: Aimer>])])
bs.save() # 保存数据,实际上调用的是 rest-framework 中的 create 方法
return Response(bs.data)
else:
return Response(bs.errors) # bs.errors 验证过后的错误信息
class BookDetailView(APIView):
# 查看指定的书籍
def get(self, request, id):
book_obj = Book.objects.filter(pk=id).first()
if book_obj:
bs = BookSerializers(book_obj)
return Response(bs.data)
else:
return Response({'static': 0, 'message': '没有该书籍'})
# 修改书籍
def put(self, request, id):
book_obj = Book.objects.filter(pk=id).first()
bs = BookSerializers(book_obj, data=request.data) # 修改数据的时候一定要指定修改那条数据(即: book_obj),否则在执行 .save() 的时候就相当于新增数据
if bs.is_valid(): # 验证数据是否有误
bs.save() # 保存数据,实际上调用的是 rest-framework 中的 update 方法
return Response(bs.data)
else:
return Response(bs.errors) # bs.errors 验证过后的错误信息
# 删除书籍
def delete(self, request, id):
Book.objects.filter(pk=id).delete()
return Response()
class PublishView(APIView):
# 查看所有出版社
def get(self, request):
publish_list = Publish.objects.all()
ps = PublishSerializers(publish_list, many=True)
return Response(ps.data)
# 添加出版社
def post(self, request):
ps = PublishSerializers(data=request.data)
if ps.is_valid(): # 验证数据是否有误
print(ps.validated_data) # 验证过后正确的数据: OrderedDict([('name', '台山出版社'), ('email', '123@qq.com')])
ps.save() # 保存数据,实际上调用的是 rest-framework 中的 create 方法
return Response(ps.data)
else:
return Response(ps.errors) # ps.errors 验证过后的错误信息
class PublishDetailView(APIView):
# 查看指定的出版社
def get(self, request, id):
publish_obj = Publish.objects.filter(pk=id).first()
if publish_obj:
ps = PublishSerializers(publish_obj)
return Response(ps.data)
else:
return Response({'static': 0, 'message': '没有该出版社'})
# 修改出版社
def put(self, request, id):
publish_obj = Publish.objects.filter(pk=id).first()
ps = PublishSerializers(publish_obj, data=request.data) # 修改数据的时候一定要指定修改那条数据(即: publish_obj),否则在执行 .save() 的时候就相当于新增数据
if ps.is_valid(): # 验证数据是否有误
ps.save() # 保存数据,实际上调用的是 rest-framework 中的 update 方法
return Response(ps.data)
else:
return Response(ps.errors) # ps.errors 验证过后的错误信息
# 删除出版社
def delete(self, request, id):
Publish.objects.filter(pk=id).delete()
return Response()
# serializer.py
from rest_framework import serializers
from .models import *
class PublishSerializers(serializers.ModelSerializer):
class Meta:
model = Publish
fields = '__all__'
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
# urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'book/$', BookView.as_view()),
url(r'book/(\d+)', BookDetailView.as_view()),
url(r'publish/$', PublishView.as_view()),
url(r'publish/(\d+)', PublishDetailView.as_view()),
]
2.mixin类编写视图
- 在 mixin 模块下提供了5个类,分别对应着 查询所有数据,查看指定数据,添加数据,修改数据,删除数据,其实这5个类就是将混合使用中的处理http请求的方法封装了起来
- mixin 模块下的5个类:
- mixins.ListModelMixin -> 查看所有数据
- mixins.ListModelMixin 下的 list() 方法: 封装了查看所有数据的代码
- mixins.CreateModelMixin -> 创建数据
- mixins.CreateModelMixin 下的create() 方法: 封装了添加数据的代码
- mixins.RetrieveModelMixin -> 查询指定数据
- mixins.RetrieveModelMixin 下的 retrieve() 方法: 封装了查询指定数据的代码
- mixins.UpdateModelMixin -> 修改数据
- mixins.UpdateModelMixin 下的 update() 方法: PUT请求 -> 封装了修改数据的代码
- mixins.UpdateModelMixin 下的 partial_update() 方法: PATCH -> 封装了修改数据的代码
- mixins.DestroyModelMixin -> 删除数据
- mixins.DestroyModelMixin 下的 destroy() 方法: 封装了删除数据的代码
- generics 模块下的 GenericAPIView 类,其实就是继承 rest-framework 中的 APIView,并且在这基础上扩展了一些方法
- 注意:
- 在使用mixin类编写视图的时候一定要继承 GenericAPIView 类
- 在使用 mixins.UpdateModelMixin 类的时候,要在操作单条数据的url中加上名为 pk 的命名匹配 -> url(r'book/(?P<pk>\d+)', BookDetailView.as_view())
- 在使用mixin类编写视图的时候一定要在该视图类下定义两个静态属性,且这两个静态属性的名字不能随便修改一定要使用这两个属性名:
- queryset -> 指定操作那张表的数据
- serializer_class -> 指定序列化类
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework import mixins
from rest_framework import generics
class BookView(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
# 不能随意修改这两个静态属性名,因为 mixins 模块下的 5 个类需要用到这两个静态属性
queryset = Book.objects.all() # 指定操作那张表的数据
serializer_class = BookSerializers # 指定序列化类
# 查看所有书籍
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs) # self.list() 是 mixins.ListModelMixin 所提供的方法,封装了查看所有数据的代码,self.list() 的返回值: Response(数据)
# 添加书籍
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs) # self.create() 是 mixins.CreateModelMixin 所提供的方法,封装了添加数据的代码,self.create() 的返回值: Response(数据)
class BookDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
# 不能随意修改这两个静态属性名,因为 mixins 模块下的 5 个类需要用到这两个静态属性
queryset = Book.objects.all() # 指定操作那张表的数据
serializer_class = BookSerializers # 指定序列化类
# 查看指定的书籍
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs) # self.retrieve() 是 mixins.RetrieveModelMixin 所提供的方法,封装了查询指定数据的代码,self.retrieve() 的返回值: Response(数据)
# 修改书籍
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs) # self.update() 是 mixins.UpdateModelMixin 所提供的方法,封装了修改数据的代码,self.update() 的返回值: Response(数据)
# 删除书籍
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) # self.destroy() 是 mixins.DestroyModelMixin 所提供的方法,封装了删除数据的代码,self.destroy() 的返回值: Response(数据)
class PublishView(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# 查看所有出版社
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
# 添加出版社
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class PublishDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# 查看指定的出版社
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
# 修改出版社
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
# 删除出版社
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
# serializer.py
from rest_framework import serializers
from .models import *
class PublishSerializers(serializers.ModelSerializer):
class Meta:
model = Publish
fields = '__all__'
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
# urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'book/$', BookView.as_view()),
url(r'book/(?P<pk>\d+)', BookDetailView.as_view()),
url(r'publish/$', PublishView.as_view()),
url(r'publish/(?P<pk>\d+)', PublishDetailView.as_view()),
]
3. 使用通用的基于类的视图
- 使用通用的基于类的视图说明:
- 简化了mixin类编写视图的操作
- generics 模块下的类的功能说明:
- GenericAPIView 继承了 APIView,且在这基础上扩展了新的功能
- ----------------------------------------------------
- generics.ListAPIView 提供了 查询所有数据 的功能
- generics.CreateAPIView 提供了 添加数据 的功能
- generics.ListCreateAPIView 提供了 查询所有数据、添加数据 的功能
- generics.RetrieveAPIView 提供了 查询指定数据 的功能
- generics.UpdateAPIView 提供了 修改数据 的功能
- generics.DestroyAPIView 提供了 删除数据 的功能
- generics.RetrieveUpdateAPIView 提供了 查询指定数据、修改数据 的功能
- generics.RetrieveDestroyAPIView 提供了 查询指定数据、删除数据 的功能
- generics.RetrieveUpdateDestroyAPIView 提供了 查询指定数据、修改数据、删除数据 的功能
- generics.ListAPIView 的说明:
- 提供了 查询所有数据 的功能
- ---------------------------------------
- 继承了 mixins.ListModelMixin
- 提供了处理 GET 请求的方法
- 处理GET请求的方法调用了 mixins.ListModelMixin 下的 list() 方法
- generics.CreateAPIView 的说明:
- 提供了 添加数据 的功能
- ---------------------------------------
- 继承了 mixins.CreateModelMixin
- 提供了处理 POST 请求的方法
- 处理POST请求的方法调用了 mixins.CreateModelMixin 下的 create() 方法
- generics.ListCreateAPIView 的说明:
- 提供了 查询所有数据、添加数据 的功能
- ---------------------------------------
- 继承了 mixins.ListModelMixin / mixins.CreateModelMixin
- 提供了处理 GET / POST 请求的方法
- 处理GET请求的方法调用了 mixins.ListModelMixin 下的 list() 方法
- 处理POST请求的方法调用了 mixins.CreateModelMixin 下的 create() 方法
- generics.RetrieveAPIView 的说明:
- 提供了 查询指定数据 的功能
- ---------------------------------------
- 继承了 mixins.RetrieveModelMixin
- 提供了处理 GET 请求的方法
- 处理GET请求的方法调用了 mixins.RetrieveModelMixin 下的 retrieve() 方法
- generics.UpdateAPIView 的说明:
- 提供了 修改数据 的功能
- ---------------------------------------
- 继承了 mixins.UpdateModelMixin
- 提供了处理 PUT / PATCH 请求的方法
- 处理PUT请求的方法调用了 mixins.UpdateModelMixin 下的 update() 方法
- 处理PATCH请求的方法调用了 mixins.UpdateModelMixin 下的 partial_update() 方法
- generics.DestroyAPIView 的说明:
- 提供了 删除数据 的功能
- ---------------------------------------
- 继承了 mixins.DestroyModelMixin
- 提供了处理 DELETE 请求的方法
- 处理DELETE请求的方法调用了 mixins.DestroyModelMixin 下的 destroy() 方法
- generics.RetrieveUpdateAPIView 的说明:
- 提供了 查询指定数据、修改数据 的功能
- ---------------------------------------
- 继承了 mixins.RetrieveModelMixin / mixins.UpdateModelMixin
- 提供了处理 GET / PUT / PATCH 请求的方法
- 处理GET请求的方法调用了 mixins.RetrieveModelMixin 下的 retrieve() 方法
- 处理PUT请求的方法调用了 mixins.UpdateModelMixin 下的 update() 方法
- 处理PATCH请求的方法调用了 mixins.UpdateModelMixin 下的 partial_update() 方法
- generics.RetrieveDestroyAPIView 的说明:
- 提供了 查询指定数据、删除数据 的功能
- ---------------------------------------
- 继承了 mixins.RetrieveModelMixin / mixins.DestroyModelMixin
- 提供了处理 GET / DELETE 请求的方法
- 处理GET请求的方法调用了 mixins.RetrieveModelMixin 下的 retrieve() 方法
- 处理DELETE请求的方法调用了 mixins.DestroyModelMixin 下的 destroy() 方法
- generics.RetrieveUpdateDestroyAPIView 的说明:
- 提供了 查询指定数据、修改数据、删除数据 的功能
- ---------------------------------------
- 继承了 mixins.RetrieveModelMixin / mixins.UpdateModelMixin / mixins.DestroyModelMixin
- 提供了处理 GET / PUT / PATCH / DELETE 请求的方法
- 处理GET请求的方法调用了 mixins.RetrieveModelMixin 下的 retrieve() 方法
- 处理PUT请求的方法调用了 mixins.UpdateModelMixin 下的 update() 方法
- 处理PATCH请求的方法调用了 mixins.UpdateModelMixin 下的 partial_update() 方法
- 处理DELETE请求的方法调用了 mixins.DestroyModelMixin 下的 destroy() 方法
- 注意:
- 一定要继承 generics 模块所提供的功能类
- 如果继承了 generics.UpdateAPIView 等涉及到修改数据的功能类,那么就要在操作单条数据的url中加上名为 pk 的命名匹配,因为修改数据的功能类继承了mixins.UpdateModelMixin类,且mixins.UpdateModelMixin类中的代码是通过“pk”这个名字去获取url中的参数,然后才能查询到指定的数据对象 -> url(r'book/(?P<pk>\d+)', BookDetailView.as_view({'请求方式': '视图类的处理请求的方法名'}))
- 在编写视图的时候一定要在该视图类下定义两个静态属性,且这两个静态属性的名字不能随便修改一定要使用这两个属性名:
- queryset -> 指定操作那张表的数据
- serializer_class -> 指定序列化类
# views.py
from .models import *
from .serializer import *
from rest_framework import generics
class BookView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
class BookDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
class PublishView(generics.ListCreateAPIView):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
class PublishDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'book/$', BookView.as_view()),
url(r'book/(?P<pk>\d+)', BookDetailView.as_view()),
url(r'publish/$', PublishView.as_view()),
url(r'publish/(?P<pk>\d+)', PublishDetailView.as_view()),
]
4.终极版
- 终极版的说明:
- 两条url使用同一个视图类
- 视图类不再继承mixins模块下的5个类,而是继承 ModelViewSet 类
- ModelViewSet类的说明:
- ModelViewSet类已经继承了mixins模块下的5个类
- .as_view() 新用法的说明:
- viewsets模块下的ViewSetMixin类通过重写 .as_view() 方法,实现了通过传递的字典({'get':'list', 'post':'create'})方式对处理http请求的方法进行分发
- 注意:
- 一定要继承 ModelViewSet 类
- 要在操作单条数据的url中加上名为 pk 的命名匹配,因为ModelViewSet类继承了mixins.UpdateModelMixin类,且mixins.UpdateModelMixin类中的代码是通过“pk”这个名字去获取url中的参数,然后才能查询到指定的数据对象 -> url(r'book/(?P<pk>\d+)', BookDetailView.as_view({'请求方式': '视图类的处理请求的方法名'}))
- 在编写视图的时候一定要在该视图类下定义两个静态属性,且这两个静态属性的名字不能随便修改一定要使用这两个属性名:
- queryset -> 指定操作那张表的数据
- serializer_class -> 指定序列化类
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# urls.py
# url(r'book/$', BookViewSet.as_view({'请求类型': '视图类的处理请求的方法名'})
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'book/$', BookViewSet.as_view({
"get": "list", # list 方法是 mixins.ListModelMixin 所提供的方法,封装了查看所有数据的代码,由于 viewsets.ModelViewSet 继承了 mixins.ListModelMixin 所以可以使用 list 方法
"post": "create" # create 方法是 mixins.CreateModelMixin 所提供的方法,封装了添加数据的代码,由于 viewsets.ModelViewSet 继承了 mixins.CreateModelMixin 所以可以使用 create 方法
})),
url(r'book/(?P<pk>\d+)', BookViewSet.as_view({
'get': 'retrieve', # retrieve 方法是 mixins.RetrieveModelMixin 所提供的方法,封装了查询指定数据的代码,由于 viewsets.ModelViewSet 继承了 mixins.RetrieveModelMixin 所以可以使用 retrieve 方法
'put': 'update', # update 方法是 mixins.UpdateModelMixin 所提供的方法,封装了修改数据的代码,由于 viewsets.ModelViewSet 继承了 mixins.UpdateModelMixin 所以可以使用 update 方法
'patch': 'partial_update', # partial_update 方法是 mixins.UpdateModelMixin 所提供的方法,封装了修改数据的代码,由于 viewsets.ModelViewSet 继承了 mixins.UpdateModelMixin 所以可以使用 partial_update 方法
'delete': 'destroy' # destroy 方法是 mixins.DestroyModelMixin 所提供的方法,封装了删除数据的代码,由于 viewsets.ModelViewSet 继承了 mixins.DestroyModelMixin 所以可以使用 destroy 方法
})),
url(r'publish/$', PublishViewSet.as_view({
"get": "list",
"post": "create"
})),
url(r'publish/(?P<pk>\d+)', PublishViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})),
]
4.重写 list、create、retrieve、update、partial_update、destroy 方法
- 如果默认返回给前端的数据结构不合适的话,可以通过重写 list 等方法重新构造数据结构
- 原理: 在视图类下重写 list 等方法,那么就不会执行 mixin 模块下的5个类所分别提供的方法(即: list、create、retrieve、update、partial_update、destroy)
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
# 重写编写 list 方法覆盖 mixins.ListModelMixin 类下的 list 方法
def list(self, request, *args, **kwargs):
ret = {
'static': 1,
'data': {
'a': 1,
'b': 2,
}
}
return Response(ret)
# 重写编写 retrieve 方法覆盖 mixins.RetrieveModelMixin 类下的 retrieve 方法
def retrieve(self, request, *args, **kwargs):
ret = {
'data': '你所查询到的单条数据'
}
return Response(ret)
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'book/$', BookViewSet.as_view({
"get": "list", # list 方法是 mixins.ListModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.ListModelMixin 所以可以使用 list 方法
"post": "create" # create 方法是 mixins.CreateModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.CreateModelMixin 所以可以使用 create 方法
})),
url(r'book/(?P<pk>\d+)', BookViewSet.as_view({
'get': 'retrieve', # retrieve 方法是 mixins.RetrieveModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.RetrieveModelMixin 所以可以使用 retrieve 方法
'put': 'update', # update 方法是 mixins.UpdateModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.UpdateModelMixin 所以可以使用 update 方法
'patch': 'partial_update', # partial_update 方法是 mixins.UpdateModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.UpdateModelMixin 所以可以使用 partial_update 方法
'delete': 'destroy' # destroy 方法是 mixins.DestroyModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.DestroyModelMixin 所以可以使用 destroy 方法
})),
url(r'publish/$', PublishViewSet.as_view({
"get": "list",
"post": "create"
})),
url(r'publish/(?P<pk>\d+)', PublishViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})),
]
.as_view() 的新用法
1..as_view() 新用法的说明
- .as_view() 新用法的作用就是两条url使用同一个视图类
- .as_view() 新用法其实就是视图四部曲中的终极版
2..as_view() 新的使用方式一
- viewsets模块下的ViewSetMixin类通过重写 .as_view() 方法,实现了通过传递的字典({'get':'list', 'post':'create'})方式对处理http请求的方法进行分发
- 通过继承 ViewSetMixin 类 和 APIView 类 实现 .as_view() 的新用法
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import viewsets
class BookViewSet(viewsets.ViewSetMixin, APIView):
def list(self, request, *args, **kwargs):
ret = {
'data': '你所查询到的所有数据'
}
return Response(ret)
def create(self, request, *args, **kwargs):
ret = {
'data': '你所添加的数据'
}
return Response(ret)
def retrieve(self, request, *args, **kwargs):
ret = {
'data': '你所查询到的单条数据'
}
print(ret)
return Response(ret)
def update(self, request, *args, **kwargs):
ret = {
'data': '你所修改的数据'
}
return Response(ret)
def partial_update(self, request, *args, **kwargs):
ret = {
'data': '你所修改的数据'
}
return Response(ret)
def destroy(self, request, *args, **kwargs):
ret = {
'data': '删除数据成功'
}
return Response(ret)
# urls.py
urlpatterns = [
url(r'book/$', BookViewSet.as_view({
"get": "list", # list 方法是 mixins.ListModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.ListModelMixin 所以可以使用 list 方法
"post": "create" # create 方法是 mixins.CreateModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.CreateModelMixin 所以可以使用 create 方法
})),
url(r'book/(?P<pk>\d+)', BookViewSet.as_view({
'get': 'retrieve', # retrieve 方法是 mixins.RetrieveModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.RetrieveModelMixin 所以可以使用 retrieve 方法
'put': 'update', # update 方法是 mixins.UpdateModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.UpdateModelMixin 所以可以使用 update 方法
'patch': 'partial_update', # partial_update 方法是 mixins.UpdateModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.UpdateModelMixin 所以可以使用 partial_update 方法
'delete': 'destroy' # destroy 方法是 mixins.DestroyModelMixin 所提供的方法,由于 viewsets.ModelViewSet 继承了 mixins.DestroyModelMixin 所以可以使用 destroy 方法
})),
]
3..as_view() 新的使用方式二
- .as_view() 的使用方式二实际上就是视图四部曲中的终极版,通过继承 ModelViewSet 类,实际上 ModelViewSet 类也是继承了 viewsets 模块下的 ViewSetMixin 类
这里就不做代码演示了不懂的可以看回 视图四部曲中的终极版 或者 查看 ModelViewSet 类的源码
认证组件
只要继承了 APIView 就可以使用认证组件
认证: 每次请求都要带着token(即: 字符串)来进行验证,如果认证成功则返回相应的数据
组件的执行顺序: 认证 -> 权限 -> 频率
1. 认证类的创建
- 认证类的固定写法一 -> 没有继承 BaseAuthentication 认证类
- 如果没有继承 BaseAuthentication 认证类,那么就需要在认证类中编写两个方法
- authenticate -> 编写认证的逻辑代码
- authenticate_header -> 不知道有啥用,反正就是要写上 rest-framework 的源码规定的,否则就会报错
- 注意:
- authenticate 方法必须有返回值
- 当验证失败的时候(即: if Fasle 的时候)要主动抛出异常,因为 rest-framework 是通过捕获异常来进行处理的
- authenticate 方法返回值说明:
- 返回 两个值 或者一个列表/元祖中包含两个值
- rest-framework 会将第一个值赋值给 request.user,第二个值会赋值给 request.auth,然后在视图类中就可以通过 request.user/auth 分别获取这两个值
- 返回 None
- 跳出当前认证,执行下一个认证
- 返回 异常
- 跳出当前认证,抛出异常信息
# rf_auth.py
from rest_framework import exceptions
class 认证类的类名(object):
def authenticate(self, request):
# 认证的逻辑代码
if True/False:
return 返回值一, 返回值二
elif xxx:
return None # 跳出本次认证
else:
# raise exceptions.AuthenticationFailed("验证失败!") # 设置报错信息
raise exceptions.AuthenticationFailed({'code': 1000, 'msg': '验证失败'}) # 设置报错信息
def authenticate_header(self, request):
pass
- 认证类的固定写法二 -> 继承 BaseAuthentication 类 -> 常用
- BaseAuthentication 认证类其实就是一个初始化好的认证类
- BaseAuthentication 认证类里面也有 authenticate 和 authenticate_header 方法,只不过这两个方法里面没有任何逻辑代码
- 在编写认证类的时候可以继承 BaseAuthentication 认证类,然后重写 authenticate 方法,而自己编写的认证类就无需写 authenticate_header 方法了,因为 BaseAuthentication 认证类里面有
- 注意:
- authenticate 方法必须有返回值
- 当验证失败的时候(即: if Fasle 的时候)要主动抛出异常,因为 rest-framework 是通过捕获异常来进行处理的
- authenticate 方法返回值说明:
- 返回 两个值 或者一个列表/元祖中包含两个值
- rest-framework 会将第一个值赋值给 request.user,第二个值会赋值给 request.auth,然后在视图类中就可以通过 request.user/auth 分别获取这两个值
- 返回 None
- 跳出当前认证,执行下一个认证
- 返回 异常
- 跳出当前认证,抛出异常信息
# rf_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
class 认证类的类名(BaseAuthentication):
def authenticate(self, request):
# 认证的逻辑代码
if True/False:
return 返回值一, 返回值二
elif xxx:
return None # 跳出本次认证
else:
# raise exceptions.AuthenticationFailed("验证失败!") # 设置报错信息
raise exceptions.AuthenticationFailed({'code': 1000, 'msg': '验证失败'}) # 设置报错信息
- 认证类的例子
# rf_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from .models import *
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get("token") # 从get请求中获取token值进行判断
token_obj = Token.objects.filter(token=token).first() # 判断是否有当前token值的记录
if token_obj:
return token_obj.user, token_obj.token
else:
# raise exceptions.AuthenticationFailed("验证失败!") # 设置报错信息
raise exceptions.AuthenticationFailed({'code': 1000, 'msg': '验证失败'}) # 设置报错信息
3. 局部认证
- 局部认证作用于当前视图的所有请求
- 局部认证的配置(即: authentication_classes = [认证类, 认证类])会覆盖掉该视图类在全局认证中的配置
- authentication_classes = [认证类, 认证类]
# rf_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from .models import *
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get("token") # 从get请求中获取token值进行判断
token_obj = Token.objects.filter(token=token).first() # 判断是否有当前token值的记录
if token_obj:
return token_obj.user, token_obj.token
else:
# raise exceptions.AuthenticationFailed("验证失败!") # 设置报错信息
raise exceptions.AuthenticationFailed({'code': 1000, 'msg': '验证失败'}) # 设置报错信息
# views.py
import hashlib
import time
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from .rf_auth import *
from rest_framework import viewsets
class TestRequestView(APIView):
authentication_classes = [TokenAuth] # 局部认证,只作用于当前视图
def get(self, request):
print(request.user) # Kevin -> user对象 -> TokenAuth 认证类下 authenticate 方法的返回值的第一个值
print(request.auth) # aec82e5c956da440c11651d0e4dfd052 -> token 值 -> TokenAuth 认证类下 authenticate 方法的返回值的第二个值
return Response('测试 request.user/auth 的值')
class BookViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuth] # 局部认证,只作用于当前视图下的所有请求
queryset = Book.objects.all()
serializer_class = BookSerializers
class PublishViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuth] # 局部认证,只作用于当前视图下的所有请求
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# 获取以用户名作为盐,以当前时间作为加密内容的字符串token
def get_token_str(username):
now_time = str(time.time())
md5 = hashlib.md5(bytes(username, encoding="utf8")) # 加盐,使用用户名作为盐,保证token的唯一性
md5.update(bytes(now_time, encoding='utf-8'))
return md5.hexdigest()
# 登陆,并且返回当前用户的token
class LoginView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
ret = {
'status': 0,
'msg': None
}
if user:
token_str = get_token_str(user.username) # 获取Token值
# 每次登陆成功后都要修改当前用户的token值
Token.objects.update_or_create(user=user, defaults={'token': token_str}) # 如果有 user=user 这条数据,那么就修改 token 值,否则就进行添加
ret['status'] = 1
ret['token'] = token_str
else:
ret['msg'] = '登陆失败'
return Response(ret)
# 接口说明: 登陆,获取token值
# 接口: http://127.0.0.1:8000/login/
# 请求类型: POST
# 发送数据的编码格式: application/json -> 即: json 格式
# 所要发送的数据: {"username":"Kevin","password":"123"}
# 结果:
{
"status": 1,
"msg": null,
"token": "4a70be5742f536bd78dad811c5044827"
}
# --------------------------------------------------------------
# 接口说明: 查看所有书籍(带token值)
# 接口: http://127.0.0.1:8000/book/?token=4a70be5742f536bd78dad811c5044827
# 请求类型: GET
# 结果:
[
{
"id": 2,
"title": "三体(第二部)",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
1,
2
]
},
……
]
# --------------------------------------------------------------
# 接口说明: 查看所有书籍(不带token值)
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
{
"detail": "验证失败!"
}
3. 局部请求认证
- 局部请求认证作用于当前视图的某几个请求
- 通过重写 rest-framework 源码中 get_authenticators 方法来实现局部请求认证
- get_authenticators 返回值格式: [认证类(), 认证类(), 认证类(), ……]
# rf_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from .models import *
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get("token") # 从get请求中获取token值进行判断
token_obj = Token.objects.filter(token=token).first() # 判断是否有当前token值的记录
if token_obj:
return token_obj.user, token_obj.token
else:
# raise exceptions.AuthenticationFailed("验证失败!") # 设置报错信息
raise exceptions.AuthenticationFailed({'code': 1000, 'msg': '验证失败'}) # 设置报错信息
# views.py
import hashlib
import time
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from .rf_auth import *
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication
from rest_framework.authentication import BasicAuthentication
class BookView(APIView):
# 局部请求认证,只作用于当前视图的某几个请求
def get_authenticators(self): # 通过重写 rest-framework 的 get_authenticators 方法从而实现局部请求认证
if self.request.method == 'POST': # 当请求是 POST 请求的时候,返回自定义的认证类,否则返回 rest-framework 默认的两个认证类
return [TokenAuth(), ] # 自定义的认证类
else:
return [SessionAuthentication(), BasicAuthentication()] # rest-framework 默认返回的两个认证类
def get(self, request, *args, **kwargs):
book_queryset = Book.objects.all()
ser = BookSerializers(instance=book_queryset, many=True)
return Response(ser.data)
def post(self, request):
ser = BookSerializers(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response(ser.errors)
# 获取以用户名作为盐,以当前时间作为加密内容的字符串token
def get_token_str(username):
now_time = str(time.time())
md5 = hashlib.md5(bytes(username, encoding="utf8")) # 加盐,使用用户名作为盐,保证token的唯一性
md5.update(bytes(now_time, encoding='utf-8'))
return md5.hexdigest()
# 登陆,并且返回当前用户的token
class LoginView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
ret = {
'status': 0,
'msg': None
}
if user:
token_str = get_token_str(user.username) # 获取Token值
# 每次登陆成功后都要修改当前用户的token值
Token.objects.update_or_create(user=user, defaults={'token': token_str}) # 如果有 user=user 这条数据,那么就修改 token 值,否则就进行添加
ret['status'] = 1
ret['token'] = token_str
else:
ret['msg'] = '登陆失败'
return Response(ret)
# 接口说明: 登陆,获取token值
# 接口: http://127.0.0.1:8000/login/
# 请求类型: POST
# 发送数据的编码格式: application/json -> 即: json 格式
# 所要发送的数据: {"username":"Kevin","password":"123"}
# 结果:
{
"status": 1,
"msg": null,
"token": "4a70be5742f536bd78dad811c5044827"
}
# --------------------------------------------------------------
# 接口说明: 查看所有书籍
# 接口: http://127.0.0.1:8000/book/
# 请求类型: GET
# 结果:
[
{
"id": 2,
"title": "三体(第二部)",
"price": 100,
"pub_date": "2012-12-12",
"publish": 1,
"authors": [
1,
2
]
},
……
]
# --------------------------------------------------------------
# 接口说明: 添加书籍(带token值)
# 接口: http://127.0.0.1:8000/book/?token=4a70be5742f536bd78dad811c5044827
# 请求类型: POST
# 结果:
{
"id": 3,
"title": "七大罪",
"price": 50,
"pub_date": "2020-04-08",
"production_date": "2020-04-08T07:06:16Z",
"publish": 1,
"authors": [
1,
2
]
}
# --------------------------------------------------------------
# 接口说明: 添加书籍(不带token值)
# 接口: http://127.0.0.1:8000/book/
# 请求类型: POST
# 结果:
{
"msg": "验证失败",
"code": "1000"
}
4. 全局认证
- 全局认证作用于所有视图类
# rf_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from .models import *
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.GET.get("token") # 从get请求中获取token值进行判断
token_obj = Token.objects.filter(token=token).first() # 判断是否有当前token值的记录
if token_obj:
return token_obj.user, token_obj.token
else:
# raise exceptions.AuthenticationFailed("验证失败!") # 设置报错信息
raise exceptions.AuthenticationFailed({'code': 1000, 'msg': '验证失败'}) # 设置报错信息
# settings.py
# 配置全局认证所需要的认证类的路径
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"]
}
- 不进行认证的视图类设置
- 如果设置了全局认证,但是又想某些视图不进行认证,那么可以在不想进行认证的视图类中设置 authentication_classes = [],因为在 rest-framework 源码中 局部认证的配置 会覆盖掉 全局认证的配置
# views.py
import hashlib
import time
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from .rf_auth import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# 获取以用户名作为盐,以当前时间作为加密内容的字符串token
def get_token_str(username):
now_time = str(time.time())
md5 = hashlib.md5(bytes(username, encoding="utf8")) # 加盐,使用用户名作为盐,保证token的唯一性
md5.update(bytes(now_time, encoding='utf-8'))
return md5.hexdigest()
# 登陆,并且返回当前用户的token
class LoginView(APIView):
authentication_classes = [] # 在全局认证下,当前视图不进行认证
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
ret = {
'status': 0,
'msg': None
}
if user:
token_str = get_token_str(user.username) # 获取Token值
# 每次登陆成功后都要修改当前用户的token值
Token.objects.update_or_create(user=user, defaults={'token': token_str}) # 如果有 user=user 这条数据,那么就修改 token 值,否则就进行添加
ret['status'] = 1
ret['token'] = token_str
else:
ret['msg'] = '登陆失败'
return Response(ret)
权限组件
只要继承了 APIView 就可以使用权限组件
组件的执行顺序: 认证 -> 权限 -> 频率
1. 权限类的创建
- 权限类的固定写法一 -> 没有继承 BasePermission 权限类
- 如果没有继承 BasePermission 权限类,那么就需要在权限类中编写两个方法
- has_permission -> 编写权限的逻辑代码
- has_object_permission -> 不知道有啥用,反正就是要写上 rest-framework 的源码规定的,否则就会报错
- 注意:
- has_permission 方法必须返回True(也可以是能代表True的元素,即: 字符串、列表、字典等)或则False(也可以是能代表True的元素,即: 0、None等),因为是 rest-framework 的源码规定的
# rf_permission.py
class 权限类的类名(object):
message = '错误信息' # 配置错误信息,如果不配置会使用默认的错误信息
def has_permission(self, request, view):
# 权限的逻辑代码
if True / False:
return True
else:
return False
def has_object_permission(self, request, view, obj):
return True
- 权限类的固定写法二 -> 继承 BasePermission 权限类 -> 常用
- BasePermission 权限类其实就是一个初始化好的权限类
- BasePermission 权限类里面也有 has_permission 和 has_object_permission 方法,只不过这两个方法里面没有任何逻辑代码
- 在编写权限类的时候可以继承 BasePermission 权限类,然后重写 has_permission 方法,而自己编写的权限类就无需写 has_object_permission 方法了,因为 BasePermission 权限类里面有
- 注意:
- has_permission 方法必须返回True(也可以是能代表True的元素,即: 字符串、列表、字典等)或则False(也可以是能代表True的元素,即: 0、None等),因为是 rest-framework 的源码规定的
# rf_permission.py
from rest_framework.permissions import BasePermission
class 权限类的类名(BasePermission):
message = '错误信息' # 配置错误信息,如果不配置会使用默认的错误信息
def has_permission(self, request, view):
# 权限的逻辑代码
if True / False:
return True
else:
return False
- 权限类的例子
# rf_permission.py
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message = "SVIP才能访问!" # 配置错误信息,如果不配置会使用默认的错误信息
def has_permission(self, request, view):
"""
:param request: APIView 的 request
:param view: 视图类的实例化对象 -> APIView 类中的 self
:return: True/False
"""
# request.user / request.auth 可以使用是因为先执行了认证类然后再执行权限类,且在执行认证类的authenticate方法的时候已经把返回值分别赋值给 request.user / request.auth
if request.user.user_type == 3:
print(request.user) # 用户的数据对象
print(request.auth) # token: 7a06fa70c1516f8cd9399e17285d9903
return True
else:
return False
2. 局部权限
- 局部权限的配置(即: permission_classes = [权限类, 权限类])会覆盖掉该视图类在全局权限中的配置
- permission_classes= [权限类, 权限类]
# rf_permission.py
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message = "SVIP才能访问!" # 配置错误信息,如果不配置会使用默认的错误信息
def has_permission(self, request, view):
"""
:param request: APIView 的 request
:param view: 视图类的实例化对象 -> APIView 类中的 self
:return: True/False
"""
# request.user / request.auth 可以使用是因为先执行了认证类然后再执行权限类,且在执行认证类的authenticate方法的时候已经把返回值分别赋值给 request.user / request.auth
if request.user.user_type == 3:
print(request.user) # 用户的数据对象
print(request.auth) # token: 7a06fa70c1516f8cd9399e17285d9903
return True
else:
return False
# views.py
from .models import *
from .serializer import *
from .rf_auth import *
from .rf_permission import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuth] # 局部认证,只作用于当前视图
permission_classes = [SVIPPermission] # 局部权限,只作用于当前视图
queryset = Book.objects.all()
serializer_class = BookSerializers
3. 全局权限
- 全局权限作用于所有视图类
# rf_permission.py
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message = "SVIP才能访问!" # 配置错误信息,如果不配置会使用默认的错误信息
def has_permission(self, request, view):
"""
:param request: APIView 的 request
:param view: 视图类的实例化对象 -> APIView 类中的 self
:return: True/False
"""
# request.user / request.auth 可以使用是因为先执行了认证类然后再执行权限类,且在执行认证类的authenticate方法的时候已经把返回值分别赋值给 request.user / request.auth
if request.user.user_type == 3:
print(request.user) # 用户的数据对象
print(request.auth) # token: 7a06fa70c1516f8cd9399e17285d9903
return True
else:
return False
# settings.py
# 配置全局权限所需要的权限类的路径
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
"DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
}
- 不进行权限认证的视图类设置
- 如果设置了全局权限,但是又想某些视图不进行权限认证,那么可以在不想进行权限认证的视图类中设置 permission_classes= [],因为在 rest-framework 源码中 局部权限的配置 会覆盖掉 全局权限的配置
# views.py
import hashlib
import time
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from .rf_auth import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# 获取以用户名作为盐,以当前时间作为加密内容的字符串token
def get_token_str(username):
now_time = str(time.time())
md5 = hashlib.md5(bytes(username, encoding="utf8")) # 加盐,使用用户名作为盐,保证token的唯一性
md5.update(bytes(now_time, encoding='utf-8'))
return md5.hexdigest()
# 登陆,并且返回当前用户的token
class LoginView(APIView):
permission_classes = [] # 在全局权限下,当前视图不进行权限认证
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
ret = {
'status': 0,
'msg': None
}
if user:
token_str = get_token_str(user.username) # 获取Token值
# 每次登陆成功后都要修改当前用户的token值
Token.objects.update_or_create(user=user, defaults={'token': token_str}) # 如果有 user=user 这条数据,那么就修改 token 值,否则就进行添加
ret['status'] = 1
ret['token'] = token_str
else:
ret['msg'] = '登陆失败'
return Response(ret)
频率组件
只要继承了 APIView 就可以使用频率组件
组件的执行顺序: 认证 -> 权限 -> 频率
1. 频率类的固定写法
# rf_throttle.py
class 频率类的类名(object):
def allow_request(self, request, view):
# 频率的逻辑代码
if True / False:
return True
else:
return False
def wait(self):
# 钩子函数(即: 回调函数)
# 当超过设置的访问频率时调用(即: 当allow_request返回False时调用)
# 返回需要等待的时间
return '时间字符串'
2. 局部频率 -> 自己编写频率类
- 局部频率的配置(即: throttle_classes = [频率类, 频率类])会覆盖掉该视图类在全局频率中的配置
- throttle_classes = [频率类, 频率类]
# rf_throttle.py
import time
VISIT_RECORD = {
# '127.0.0.1:8080':[最新的时间,……,最初的时间]
}
class MyThrottle(object):
"""
一分钟允许访问5次
原理:
拿到用户IP地址
编写访问记录 {'ip地址':[最新的时间,……,最初的时间]}
确保用户所对应的记录列表的最新时间和最初时间的时间差 <= 规定时间
判断用户所对应的记录列表的长度 < 允许访问次数
"""
allow_time = 60 # 时间
visits = 5 # 允许访问次数
def __init__(self):
self.history_list = []
def allow_request(self, request, view):
"""
:param request: APIView 的 request
:param view: 视图类的实例化对象 -> APIView 类中的 self
:return: True/False
"""
# 获取用户的IP地址
ip = request.META.get('REMOTE_ADDR', '')
if ip not in VISIT_RECORD:
VISIT_RECORD[ip] = [time.time()]
else:
history = VISIT_RECORD[ip]
if len(history) <= self.visits:
history.insert(0, time.time()) # 将最新的时间添加到列表的第一位
self.history_list = history # 因为 history 是列表,所以 self.history_list = history 是引用关系,没有进行深度拷贝,所以下面 history.pop() 执行 self.history_list 无需重新赋值
while time.time() - history[-1] > self.allow_time:
# 确保列表时间是允许的范围内(即: 列表的时间差不能超过60秒)
# 循环计算最新的时间和最初的时间是否在60秒,如果不是删除则删除最初的时间
history.pop()
# 判断列表的长度有没有超过允许访问次数
if len(history) > self.visits:
return False
return True
def wait(self):
# 钩子函数(即: 回调函数)
# 当超过设置的访问频率时调用(即: 当allow_request返回False时调用)
# 返回值等待时间
return self.allow_time - (time.time() - self.history_list[-1])
# views.py
from .models import *
from .serializer import *
from .rf_auth import *
from .rf_permission import *
from .rf_throttle import *
from rest_framework import viewsets
from rest_framework import exceptions
# 修改频率组件的错误信息
class MyExceptions(exceptions.Throttled):
default_detail = '连接次数过多'
extra_detail_plural = extra_detail_singular = '请在{wait}秒后访问'
def __init__(self, wait=None, detail=None, code=None):
super().__init__(wait=wait, detail=detail, code=code)
class BookViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuth] # 局部认证,只作用于当前视图
permission_classes = [SVIPPermission] # 局部权限,只作用于当前视图
throttle_classes = [MyThrottle] # 局部频率,只作用于当前视图
queryset = Book.objects.all()
serializer_class = BookSerializers
# 修改频率组件的错误信息
def throttled(self, request, wait):
raise MyExceptions(wait=wait)
2. 全局频率 -> 自己编写频率类
- 全局频率作用于所有视图类
# rf_throttle.py
import time
VISIT_RECORD = {
# '127.0.0.1:8080':[最新的时间,……,最初的时间]
}
class MyThrottle(object):
"""
一分钟允许访问5次
原理:
拿到用户IP地址
编写访问记录 {'ip地址':[最新的时间,……,最初的时间]}
确保用户所对应的记录列表的最新时间和最初时间的时间差 <= 规定时间
判断用户所对应的记录列表的长度 < 允许访问次数
"""
allow_time = 60 # 时间
visits = 5 # 允许访问次数
def __init__(self):
self.history_list = []
def allow_request(self, request, view):
"""
:param request: APIView 的 request
:param view: 视图类的实例化对象 -> APIView 类中的 self
:return: True/False
"""
# 获取用户的IP地址
ip = request.META.get('REMOTE_ADDR', '')
if ip not in VISIT_RECORD:
VISIT_RECORD[ip] = [time.time()]
else:
history = VISIT_RECORD[ip]
if len(history) <= self.visits:
history.insert(0, time.time()) # 将最新的时间添加到列表的第一位
self.history_list = history # 因为 history 是列表,所以 self.history_list = history 是引用关系,没有进行深度拷贝,所以下面 history.pop() 执行 self.history_list 无需重新赋值
while time.time() - history[-1] > self.allow_time:
# 确保列表时间是允许的范围内(即: 列表的时间差不能超过60秒)
# 循环计算最新的时间和最初的时间是否在60秒,如果不是删除则删除最初的时间
history.pop()
# 判断列表的长度有没有超过允许访问次数
if len(history) > self.visits:
return False
return True
def wait(self):
# 钩子函数(即: 回调函数)
# 当超过设置的访问频率时调用(即: 当allow_request返回False时调用)
# 返回值等待时间
return self.allow_time - (time.time() - self.history_list[-1])
# settings.py
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
# "DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
"DEFAULT_THROTTLE_CLASSES": ["app01.rf_throttle.MyThrottle"] # 全局频率
}
- 不进行频率认证的视图类设置
- 如果设置了全局频率,但是又想某些视图不进行频率认证,那么可以在不想进行频率认证的视图类中设置 throttle_classes = [],因为在 rest-framework 源码中 局部频率的配置 会覆盖掉 全局频率的配置
# views.py
import hashlib
import time
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from .rf_auth import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializers
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
# 获取以用户名作为盐,以当前时间作为加密内容的字符串token
def get_token_str(username):
now_time = str(time.time())
md5 = hashlib.md5(bytes(username, encoding="utf8")) # 加盐,使用用户名作为盐,保证token的唯一性
md5.update(bytes(now_time, encoding='utf-8'))
return md5.hexdigest()
# 登陆,并且返回当前用户的token
class LoginView(APIView):
throttle_classes = [] # 在全局频率下,当前视图不进行频率认证
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
ret = {
'status': 0,
'msg': None
}
if user:
token_str = get_token_str(user.username) # 获取Token值
# 每次登陆成功后都要修改当前用户的token值
Token.objects.update_or_create(user=user, defaults={'token': token_str}) # 如果有 user=user 这条数据,那么就修改 token 值,否则就进行添加
ret['status'] = 1
ret['token'] = token_str
else:
ret['msg'] = '登陆失败'
return Response(ret)
4. 局部频率 -> 使用 rest-framework 所提供的频率类
# rf_throttle.py
from rest_framework.throttling import SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
rate = '5/m' # 每分钟允许访问5次
# 获取用户IP地址
def get_cache_key(self, request, view):
return self.get_ident(request)
# views.py
from .rf_throttle import *
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
throttle_classes = [MyThrottle]
queryset = Book.objects.all()
serializer_class = BookSerializers
5. 全局频率 -> 使用 rest-framework 所提供的频率类
# rf_throttle.py
from rest_framework.throttling import SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
scope = 'visit_rate' # 设置全局频率的属性名
# 获取用户IP地址
def get_cache_key(self, request, view):
return self.get_ident(request)
# settings.py
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
# "DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
"DEFAULT_THROTTLE_CLASSES": ["app01.rf_throttle.MyThrottle"], # 全局频率
"DEFAULT_THROTTLE_RATES": {
'visit_rate': '5/m' # 每分钟允许访问5次
}
}
分页
只要继承了 APIView 就可以使用分页组件
1.PageNumberPagination 的分页类使用
- 方式一
- 视图四部曲中在混合使用方式中使用分页
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
page_size = 1 # 每页显示多少条数据
page_query_param = 'page' # url的参数名,即: ?page=1
page_size_query_param = 'size' # url的参数名,即: ?page=1&size=2,如果在url加上该参数就代表临时显示2条数据
max_page_size = 5 # 设置最大的临时显示数,一般和 page_size_query_param 搭配使用,即: size 所设置的数值不能超过 max_page_size 所设置的数值
class PublishView(APIView):
# 查看所有出版社
def get(self, request):
publish_list = Publish.objects.all()
# 分页
pnp = MyPageNumberPagination() # 实例化分页对象
publish_list_page = pnp.paginate_queryset(publish_list, request, self) # 使用分页对象下的 paginate_queryset 方法处理 queryset 对象(即: 查询到的数据列表)
ps = PublishSerializers(publish_list_page, many=True) # 序列化被 paginate_queryset 方法处理后的 queryset 对象(即: 查询到的数据列表)
return Response(ps.data)
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?page=1
# 请求类型: GET
# 分页说明: 每页显示1条数据
# 结果:
[
{
"id": 1,
"name": "东莞出版社",
"email": "123@qq.com"
}
]
# --------------------------------------------------------------
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?page=1&size=3
# 请求类型: GET
# 分页说明: 每页显示1条数据,临时显示3条数据
# 结果:
[
{
"id": 1,
"name": "东莞出版社",
"email": "123@qq.com"
},
{
"id": 2,
"name": "广州出版社",
"email": "123@qq.com"
},
{
"id": 4,
"name": "深圳出版社",
"email": "123@qq.com"
}
]
- 方式二
- 视图四部曲中除了混合使用方式外的其他方式使用分页
- pagination_class = 分页类
# views.py
from .models import *
from .serializer import *
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
page_size = 1 # 每页显示多少条数据
page_query_param = 'page' # url的参数名,即: ?page=1
page_size_query_param = 'size' # url的参数名,即: ?page=1&size=2,如果在url加上该参数就代表临时显示2条数据
max_page_size = 5 # 设置最大的临时显示数,一般和 page_size_query_param 搭配使用,即: size 所设置的数值不能超过 max_page_size 所设置的数值
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
pagination_class = MyPageNumberPagination
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?page=1
# 请求类型: GET
# 分页说明: 每页显示1条数据
# 结果:
{
"count": 11, # 总页数
"next": "http://127.0.0.1:8000/publish/?page=2", # 下一页的网址,没有则为 null
"previous": null, # 上一页的网址,没有则为 null
"results": [
{
"id": 1,
"name": "东莞出版社",
"email": "123@qq.com"
}
]
}
# --------------------------------------------------------------
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?page=1&size=3
# 请求类型: GET
# 分页说明: 每页显示1条数据,临时显示3条数据
# 结果:
{
"count": 11, # 总页数
"next": "http://127.0.0.1:8000/publish/?page=2&size=3", # 下一页的网址,没有则为 null
"previous": null, # 上一页的网址,没有则为 null
"results": [
{
"id": 1,
"name": "东莞出版社",
"email": "123@qq.com"
},
{
"id": 2,
"name": "广州出版社",
"email": "123@qq.com"
},
{
"id": 4,
"name": "深圳出版社",
"email": "123@qq.com"
}
]
}
- 配置全局的分页参数 -> 不建议使用
- 如果配置了全局的分页参数可以不用创建分页类,直接使用 PageNumberPagination 分页类
- 只有 PAGE_SIZE 这个参数可以进行设置
# settings.py
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
# "DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
# 'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser'] # 全局解析器
'PAGE_SIZE': 1, # 分页 -> 一页显示多少条数据
}
# views.py
# 方式一
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework.pagination import PageNumberPagination
class PublishView(APIView):
# 查看所有出版社
def get(self, request):
publish_list = Publish.objects.all()
# 分页
pnp = PageNumberPagination() # 实例化分页对象
publish_list_page = pnp.paginate_queryset(publish_list, request, self) # 使用分页对象下的 paginate_queryset 方法处理 queryset 对象(即: 查询到的数据列表)
ps = PublishSerializers(publish_list_page, many=True) # 序列化被 paginate_queryset 方法处理后的 queryset 对象(即: 查询到的数据列表)
return Response(ps.data)
# -----------------------------------------
# 方式二
from .models import *
from .serializer import *
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
pagination_class = PageNumberPagination
2.LimitOffsetPagination 的偏移分页类使用
- LimitOffsetPagination的使用方式和PageNumberPagination的使用方式是一样,只是LimitOffsetPagination分页类的参数和功能不一样而已
- 方式一
- 视图四部曲中在混合使用方式中使用分页
# views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 2 # 显示多少条数据
limit_query_param = 'limit' # url的参数名,即: ?limit=2
offset_query_param = 'offset' # url的参数名,即: ?limit=2&offset=1,如果在url加上该参数就代表从1开始偏移几位
max_limit = 5 # 限制最多显示多少条数据
class PublishView(APIView):
# 查看所有出版社
def get(self, request):
publish_list = Publish.objects.all()
# 分页
lop = MyLimitOffsetPagination() # 实例化分页对象
publish_list_page = lop.paginate_queryset(publish_list, request, self) # 使用分页对象下的 paginate_queryset 方法处理 queryset 对象(即: 查询到的数据列表)
ps = PublishSerializers(publish_list_page, many=True) # 序列化被 paginate_queryset 方法处理后的 queryset 对象(即: 查询到的数据列表)
return Response(ps.data)
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?offset=2
# 请求类型: GET
# 分页说明: 显示2条数据,从1开始偏移2条数据
# 结果:
[
{
"id": 3,
"name": "深圳出版社",
"email": "123@qq.com"
},
{
"id": 4,
"name": "深圳出版社",
"email": "123@qq.com"
}
]
# --------------------------------------------------------------
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?limit=3&offset=2
# 请求类型: GET
# 分页说明: 显示3条数据,从1开始偏移2条数据
# 结果:
[
{
"id": 3,
"name": "深圳出版社",
"email": "123@qq.com"
},
{
"id": 4,
"name": "深圳出版社",
"email": "123@qq.com"
},
{
"id": 5,
"name": "深圳出版社",
"email": "123@qq.com"
}
]
- 方式二
- 视图四部曲中除了混合使用方式外的其他方式使用分页
- pagination_class = 分页类
# views.py
from .models import *
from .serializer import *
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 2 # 显示多少条数据
limit_query_param = 'limit' # url的参数名,即: ?limit=2
offset_query_param = 'offset' # url的参数名,即: ?limit=2&offset=1,如果在url加上该参数就代表从1开始偏移几位
max_limit = 5 # 限制最多显示多少条数据
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
pagination_class = MyLimitOffsetPagination
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?offset=1
# 请求类型: GET
# 分页说明: 默认显示2条数据,从1开始偏移1条数据
# 结果:
{
"count": 11, # 总页数
"next": "http://127.0.0.1:8000/publish/?limit=2&offset=3", # 下一页的网址,没有则显示null
"previous": "http://127.0.0.1:8000/publish/?limit=2", # 上一页的网址,没有则显示null
"results": [
{
"id": 2,
"name": "广州出版社",
"email": "123@qq.com"
},
{
"id": 4,
"name": "深圳出版社",
"email": "123@qq.com"
}
]
}
# --------------------------------------------------------------
# 接口说明: 查看出版社(带分页)
# 接口: http://127.0.0.1:8000/publish/?limit=3&offset=2
# 请求类型: GET
# 分页说明: 显示3条数据,从1开始偏移2条数据
# 结果:
{
"count": 11, # 总页数
"next": "http://127.0.0.1:8000/publish/?limit=3&offset=5", # 下一页的网址,没有则显示null
"previous": "http://127.0.0.1:8000/publish/?limit=3", # 上一页的网址,没有则显示null
"results": [
{
"id": 4,
"name": "深圳出版社",
"email": "123@qq.com"
},
{
"id": 5,
"name": "深圳出版社",
"email": "123@qq.com"
},
{
"id": 6,
"name": "深圳出版社",
"email": "123@qq.com"
}
]
}
- 配置全局的分页参数 -> 不建议使用
- 如果配置了全局的分页参数可以不用创建分页类,直接使用 LimitOffsetPagination 分页类
- 只有 PAGE_SIZE 这个参数可以进行设置
- 这里的 PAGE_SIZE 参数指的是 default_limit 参数
# settings.py
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
# "DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
# 'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser'] # 全局解析器
'PAGE_SIZE': 2, # 分页 -> 显示多少条数据
}
# views.py
# 方式一
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from .serializer import *
from rest_framework.pagination import LimitOffsetPagination
class PublishView(APIView):
# 查看所有出版社
def get(self, request):
publish_list = Publish.objects.all()
# 分页
lop = LimitOffsetPagination() # 实例化分页对象
publish_list_page = lop.paginate_queryset(publish_list, request, self) # 使用分页对象下的 paginate_queryset 方法处理 queryset 对象(即: 查询到的数据列表)
ps = PublishSerializers(publish_list_page, many=True) # 序列化被 paginate_queryset 方法处理后的 queryset 对象(即: 查询到的数据列表)
return Response(ps.data)
# -------------------------------------------
# 方式二
from .models import *
from .serializer import *
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination
class PublishViewSet(viewsets.ModelViewSet):
queryset = Publish.objects.all()
serializer_class = PublishSerializers
pagination_class = LimitOffsetPagination
版本
只要继承了 APIView 就可以使用版本
1. 版本的作用
- 根据版本的不同返回相应的数据结构
- 限制只能使用哪几个版本的接口
2. 局部版本
- 局部版本的配置(即: versioning_class = 版本类)会覆盖掉该视图类在全局版本中的配置
- versioning_class = 版本类
- QueryParameterVersioning 版本类 -> 从 url 参数中获取版本号
- url版本参数的默认参数名: version
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning
class BookViewSet(APIView):
versioning_class = QueryParameterVersioning # 局部版本
def get(self, request, *args, **kwargs):
print(request.version) # v1 -> 获取当前版本号
print(request.versioning_scheme) # QueryParameterVersioning 的实例化对象
if request.version == 'v1':
return Response('v1版本的数据')
else:
return Response('其他版本的数据')
- URLPathVersioning 版本类 -> 使用url正则匹配获取版本号 -> 推荐使用
- 命名匹配的默认名称: version
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning
class BookViewSet(APIView):
versioning_class = URLPathVersioning # 局部版本
def get(self, request, *args, **kwargs):
print(request.version) # v1 -> 获取当前版本号
print(request.versioning_scheme) # QueryParameterVersioning 的实例化对象
if request.version == 'v1':
return Response('v1版本的数据')
else:
return Response('其他版本的数据')
- 注意: url必须使用命名匹配
# urls.py
urlpatterns = [
# url(r'api/(?P<version>[v1|v2]+)/book', BookViewSet.as_view()),
# 等同于
url(r'api/(?P<version>\w+)/book', BookViewSet.as_view()), # 使用该正则的前提必须设置了允许的版本
]
- request.version 和 request.versioning_scheme 的说明
- 如果使用了版本,那么 request 就会被绑定两个属性: version 和 versioning_scheme
- version: 当前版本,用于根据版本的不同返回相应的数据结构
- versioning_scheme: 当前使用的版本类的实例化对象
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning
class BookViewSet(APIView):
versioning_class = URLPathVersioning
def get(self, request, *args, **kwargs):
print(request.version) # v1 -> 获取当前版本号
print(request.versioning_scheme) # QueryParameterVersioning 的实例化对象
if request.version == 'v1':
return Response('v1版本的数据')
else:
return Response('其他版本的数据')
- version 参数名的修改
- version 参数名必须要在全局设置中修改(即: settings.py)
- QueryParameterVersioning 的 url版本参数名 “version” 和 URLPathVersioning 命名匹配的 “version”,使用的都是同一个配置项中的参数名
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": 'v', # 参数名
}
- 默认版本号
- 该设置必须在全局中进行设置(即: settings.py)
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": 'version', # 参数名
'DEFAULT_VERSION': 'v1', # 默认版本号
}
- 允许的版本
- 说明: 允许调用当前接口的哪些版本
- 该设置必须在全局中进行设置(即: settings.py)
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": 'version', # 参数名
'DEFAULT_VERSION': 'v1', # 默认版本号
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
}
2. 全局版本
- 全局版本作用于所有视图类
- 可以通过设置局部版本来覆盖全局版本的设置
# settings.py
REST_FRAMEWORK = {
……
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning", # 使用什么版本类
"VERSION_PARAM": 'version', # 参数名
'DEFAULT_VERSION': 'v1', # 默认版本号
'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
}
解析器
1. 解析器的说明
- 根据 Content-Type 类型对发送过来的数据进行解析
- 可以通过编写中间件的方式实现解析器的功能
- rest_framework 提供了4个解析器:
- JSONParser -> Content-Type = application/json
- FormParser -> Content-Type = application/x-www-form-urlencoded
- MultiPartParser -> Content-Type = multipart/form-data
- FileUploadParser -> Content-Type = */*
- 在使用 rest_framework 的时候所有视图类默认支持 JSONParser、FormParser、MultiPartParser 解析器
2. 局部解析器
- 局部解析器的配置(即: parser_classes = [解析器, 解析器])会覆盖掉该视图类在全局解析器中的配置
- parser_classes = [解析器, 解析器]
- 注意: 在日常开发中尽量不要去配置局部解析器和全局解析器,因为 rest_framework 默认所设置的3个解析器已经够用了
# views.py
from rest_framework import viewsets
from rest_framework.parsers import JSONParser, FormParser # 导入解析器
class BookViewSet(viewsets.ModelViewSet):
parser_classes = [JSONParser] # 该视图类只使用 JSONParser 解析器
queryset = Book.objects.all()
serializer_class = BookSerializers
3. 全局解析器
- 全局解析器的设置作用于所有视图类
- 可以通过设置局部解析器来覆盖全局解析器的设置
# settings.py
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
# "DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser'] # 全局解析器
}
渲染器
1. 渲染器的说明
- 渲染器就是设置所要返回的数据在浏览器的显示格式
- 注意: 不要使用 JSONRenderer 除外的渲染器,因为容易出 BUG (通俗理解: 只使用 JSONRenderer 渲染器)
2. 局部渲染器
- 局部渲染器的配置(即: renderer_classes = [渲染器, 渲染器])会覆盖掉该视图类在全局渲染器中的配置
- renderer_classes = [渲染器, 渲染器]
# views.py
from .serializer import *
from .models import *
from rest_framework import viewsets
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer, AdminRenderer
class BookViewSet(viewsets.ModelViewSet):
"""
常用的渲染器:
JSONRenderer: 显示JSON格式(即: 没有任何样式)
BrowsableAPIRenderer: 使用 rest-framework 所提供的 html 模板显示 JSON 格式,默认显示格式
AdminRenderer: 显示表格的形式
"""
# renderer_classes = [JSONRenderer, BrowsableAPIRenderer, AdminRenderer] # 局部渲染器,同时使用 JSONRenderer、BrowsableAPIRenderer、AdminRenderer 渲染器
renderer_classes = [JSONRenderer] # 该视图类只使用 JSONRenderer 渲染器
queryset = Book.objects.all()
serializer_class = BookSerializers
- JSONRenderer 在浏览器的显示效果

- BrowsableAPIRenderer 在浏览器的显示效果,默认显示格式

- AdminRenderer 在浏览器的显示效果

3. 全局渲染器
- 全局渲染器的设置作用于所有视图类
- 可以通过设置局部渲染器来覆盖全局渲染器的设置
# settings.py
REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.rf_auth.TokenAuth"], # 全局认证
# "DEFAULT_PERMISSION_CLASSES": ["app01.rf_permission.SVIPPermission"] # 全局权限
# "DEFAULT_THROTTLE_CLASSES": ["app01.rf_throttle.MyThrottle"], # 全局频率
# "DEFAULT_RENDERER_CLASSES": ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', 'rest_framework.renderers.AdminRenderer'] # 全局渲染器
"DEFAULT_RENDERER_CLASSES": ['rest_framework.renderers.JSONRenderer'] # 全局渲染器
}
注册url的新方法
- rest-framework 提供了新的url注册方法
- 使用前提: 视图类必须继承 ModelViewSet(即: 必须使用视图四部曲中的终极版)
- rest-framework 的url注册方法会在原本的两条url基础上在添加多两条url
# urls.py
from django.conf.urls import url, include
from django.contrib import admin
from app01.views import *
from rest_framework import routers
# 实例化默认路由对象
router = routers.DefaultRouter()
# 注册路由:
# 默认路由对象.register('url前缀', 视图类)
# url前缀是一个字符串,而不是一个url正则路径
router.register(r'book', BookViewSet)
router.register(r'publish', PublishViewSet)
urlpatterns = [
url(r'', include(router.urls)), # 注册路由
url(r'login', LoginView.as_view()),
]
- 所生成的url正则路径
^book/$ [name='book-list'] # 平时我们使用的操作或查看所有数据的url
^book\.(?P<format>[a-z0-9]+)/?$ [name='book-list'] # rest-framework 新增的
^book/(?P<pk>[^/.]+)/$ [name='book-detail'] # 平时我们使用的操作或查看指定数据的url
^book/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='book-detail'] # rest-framework 新增的
^publish/$ [name='publish-list'] # 平时我们使用的操作或查看所有数据的url
^publish\.(?P<format>[a-z0-9]+)/?$ [name='publish-list'] # rest-framework 新增的
^publish/(?P<pk>[^/.]+)/$ [name='publish-detail'] # 平时我们使用的操作或查看指定数据的url
^publish/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='publish-detail'] # rest-framework 新增的
- 所生成url路径
http://127.0.0.1:8000/book/
http://127.0.0.1:8000/book.json
http://127.0.0.1:8000/book/2/
http://127.0.0.1:8000/book/2.json
http://127.0.0.1:8000/publish/
http://127.0.0.1:8000/publish.json
http://127.0.0.1:8000/publish/1/
http://127.0.0.1:8000/publish/1.json
# 在url正则路径中没有,但是 rest-framework 默认添加上的url路径
http://127.0.0.1:8000/book/?format=api
http://127.0.0.1:8000/book/?format=json
http://127.0.0.1:8000/book/2/?format=api
http://127.0.0.1:8000/book/2/?format=json